// Copyright 2014 The Go Circuit Project
// Use of this source code is governed by the license for
// The Go Circuit Project, found in the LICENSE file.
//
// Authors:
//   2014 Chris Monson <chris@gocircuit.org>

/*
The Config FID pattern is like a linux proc file.
It can be written to (and parsed into something) and read from (formatted from
structured data). Writing to it typically affects the configuration of a ripple.
*/

package ripple

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"math/rand"
	"sync"
	"time"

	"circuit/kit/rh"
)

type ParserFormatter interface {
	Parse(b []byte) error
	Format() []byte
}

type configFID struct {
	rh.AnchoredFID
	sync.Mutex

	name string

	parse  func([]byte) error
	format func() []byte

	q   rh.Q
	gtm time.Time

	output *bytes.Buffer
	input  *bytes.Buffer
}

func NewConfigFID(name string, parse func([]byte) error, format func() []byte) rh.FID {
	return &configFID{
		name:   name,
		parse:  parse,
		format: format,
		q: rh.Q{
			ID:  uint64(rand.Int63()),
			Ver: 1,
		},
	}
}

func (c *configFID) String() string {
	return fmt.Sprintf("%s (%p) %v", c.name, c, c.Q())
}

func (c *configFID) Q() rh.Q {
	return c.q
}

func (c *configFID) Open(flag rh.Flag, intr rh.Intr) error {
	return nil
}

func (c *configFID) Read(offset int64, count int, intr rh.Intr) (rh.Chunk, error) {
	defer un(lock(c))
	log.Printf("%s Read: %d %d", c.name, offset, count)

	// First attempt at reading, we create a buffer and populate it with the
	// formatted config. We also add a trailing newline.
	if c.output == nil {
		c.output = bytes.NewBuffer(c.format())
		c.output.WriteByte('\n')
	}

	// Special case for returning all (remaining) data.
	if count == 0 {
		chunk := rh.ByteChunk(c.output.Bytes())
		c.output.Reset()
		return chunk, nil
	}

	buff := make([]byte, count)
	n, err := c.output.Read(buff) // ignore offset
	if err == io.EOF {
		err = nil // Fuse gets very confused about EOF errors
	}
	return rh.ByteChunk(buff[:n]), err
}

func (c *configFID) Write(offset int64, data rh.Chunk, intr rh.Intr) (int, error) {
	defer un(lock(c))

	if c.input == nil {
		c.input = bytes.NewBuffer([]byte{})
	}
	return c.input.Write(data.(rh.ByteChunk)[offset:])
}

func (c *configFID) Clunk() {
	defer un(lock(c))
	if c.input == nil || c.input.Len() == 0 {
		return
	}
	if err := c.parse(c.input.Bytes()); err != nil {
		log.Printf("Error parsing %s config: %v", c.name, err)
		return
	}
}

func (c *configFID) Walk(wname []string) (rh.FID, error) {
	if len(wname) > 0 {
		return nil, rh.ErrClash
	}
	return NewConfigFID(c.name, c.parse, c.format), nil
}

func (c *configFID) Stat() (*rh.Dir, error) {
	return &rh.Dir{
		Q:      c.q,
		Mode:   rh.Mode{Attr: rh.ModeIO},
		Perm:   0666, // rw-rw-rw-
		Atime:  c.gtm,
		Mtime:  c.gtm,
		Name:   c.name,
		Length: 0, // TODO(chris): emit a real size?
		Uid:    uid(),
		Gid:    gid(),
	}, nil
}
